package cn.tedu.csmall.product.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 统计各Service方法执行耗时的切面
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Slf4j
@Aspect // 切面
@Component // 组件
public class TimerAspect {

    // =========================================================
    // 【注意】此类只是用于演示Spring AOP的一个DEMO类
    // =========================================================


    public TimerAspect() {
        log.debug("创建切面类对象：TimerAspect");
    }

    // 连接点（JoinPoint）：程序执行过程中的某个节点，例如调用了某个方法
    // 切入点（PointCut）：选择1个或多个连接点的表达式
    // -----------------------------------------------------------
    // 通知（Advice）注解
    // @Around：环绕连接点，即包裹了连接点，你写的代码可以在连接点之前和之后执行
    // @Before：在连接点之前执行
    // @AfterReturning：仅当连接点方法正常返回后执行，也就是说，当抛出异常时不会执行
    // @AfterThrowing：仅当连接点方法抛出异常时执行，也就是说，当方法没有抛出异常，而是正常返回时并不会执行
    // @After：在连接点之后执行
    // 以上各Advice执行例如：
    // @Around
    // try {
    //     @Before
    //     执行连接点
    //     @AfterReturning
    // } catch (Throwable e) {
    //     @AfterThrowing
    // } finally {
    //     @After
    // }
    // @Around
    // -----------------------------------------------------------
    // execution()的配置是使用AOP时的“切入点表达式”，用于匹配某些方法
    // 在切入点表达式中，可以使用通配符
    // -- 星号：任意1次匹配
    // -- 2个连续的小数点：任意n次匹配，仅能用于包名和参数列表
    //                 ↓ 返回值类型
    //                   ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 包名
    //                                                  ↓ 类名
    //                                                    ↓ 方法名
    //                                                      ↓↓ 参数列表
    // 另外，在表达式中，方法的返回值类型左侧还可以指定修饰符，修饰符是可选的
    // 注解是典型的修饰符之一
    @Around("execution(* cn.tedu.csmall.product.service.*.*(..))")
    public Object timer(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("TimerAspect切面中的方法开始执行");
        String targetClassName = pjp.getTarget().getClass().getName();// 类型
        String signatureName = pjp.getSignature().getName();// 方法的声明中的方法名
        Object[] args = pjp.getArgs();// 方法的参数列表
        System.out.println("类型：" + targetClassName);
        System.out.println("方法名：" + signatureName);
        System.out.println("参数：" + Arrays.toString(args));

        // 记录开始时间
        long start = System.currentTimeMillis();

        // 执行连接点方法
        // 注意：调用proceed()方法时，必须获取返回值，并作为当前切面方法的返回值
        // 注意：调用proceed()方法时，必须抛出异常，或在捕获到异常后再次抛出异常，否则，Controller将无法知晓曾经出现过异常，更不会向客户端响应错误信息
        Object result = pjp.proceed();

        // 记录结束时间
        long end = System.currentTimeMillis();

        // 显示执行耗时
        System.out.println("执行耗时：" + (end - start));

        // 必须返回调用proceed()方法的结果
        return result;
    }

}
